#ifndef __IOSCHEDULER_H__
#define __IOSCHEDULER_H__

#include "scheduler.h"
#include "timer.h"

namespace lzp {

// work flow
// 1 register one event -> 2 wait for it to ready -> 3 schedule the callback -> 4 unregister the event -> 5 run the callback
class IOManager : public Scheduler, public TimerManager {
public:
    /**
     * @brief IO事件，继承自epoll对事件的定义
     * @details 这里只关心socket fd的读和写事件，其他事件会归类到这两类事件中
     */
    enum Event {
        NONE = 0x0,
        // READ == EPOLLIN
        READ = 0x1,
        // WRITE == EPOLLOUT
        WRITE = 0x4
    };

private:
    /**
    * @brief socket fd上下文
    * @details 每个socket fd都对应⼀个FdContext，包括fd的值，fd上的事件，以及fd的读写事件上下⽂
    */
    struct FdContext {
        /**
         * @brief 事件上下文
         * @details fd的每个事件都有⼀个事件上下文，保存这个事件的回调函数以及执行回调函数的调度器
         * 这里对fd事件做了简化，只预留了读事件和写事件，所有的事件都被归类到这两类事件中
         */
        struct EventContext {
            // shceduler
            Scheduler *scheduler = nullptr;
            // callback fiber
            std::shared_ptr<Fiber> fiber;
            // callback function
            std::function<void()> cb;
        };

        // 读事件上下文
        EventContext read;
        // 写事件上下文
        EventContext write;
        // 事件关联的句柄
        int fd = 0;
        // 该fd注册了哪些事件的回调函数，或者说fd关心哪些事件
        Event events = NONE;
        // 事件的mutex
        std::mutex mutex;

        /**
         * @brief 获取事件上下⽂类
         * @param[in] event 事件类型
         * @return 返回对应事件的上下⽂
         */
        EventContext& getEventContext(Event event);

        /**
         * @brief 重置事件上下⽂
         * @param[in, out] ctx 待重置的事件上下⽂对象
         */
        void resetEventContext(EventContext& ctx);

        /**
         * @brief 触发事件
         * @details 根据事件类型调⽤对应上下⽂结构中的调度器去调度回调协程或回调函数
         * @param[in] event 事件类型
         */
        void triggerEvent(Event event);
    };

public:
    /**
     * @brief 构造函数
     * @param[in] threads 线程数量
     * @param[in] use_caller 是否将调⽤线程包含进去
     * @param[in] name 调度器的名称
     */
    IOManager(size_t threads = 1, bool use_caller = true, const std::string& name = "IOManager");
    

    ~IOManager();

    /**
     * @brief 添加事件
     * @details fd描述符发⽣了event事件时执⾏cb函数
     * @param[in] fd socket句柄
     * @param[in] event 事件类型
     * @param[in] cb 事件回调函数，如果为空，则默认把当前协程作为回调执⾏体
     * @return 添加成功返回0,失败返回-1
     */
    int addEvent(int fd, Event event, std::function<void()> cb = nullptr);
    
    /**
     * @brief 删除事件
     * @param[in] fd socket句柄
     * @param[in] event 事件类型
     * @attention 不会触发事件
     * @return 是否删除成功
     */
    bool delEvent(int fd, Event event);

    /**
     * @brief 取消事件
     * @param[in] fd socket句柄
     * @param[in] event 事件类型
     * @attention 如果该事件被注册过回调，那就触发⼀次回调事件
     * @return 是否删除成功
     */
    bool cancelEvent(int fd, Event event);
    
    /**
     * @brief 取消所有事件
     * @details 所有被注册的回调事件在cancel之前都会被执⾏⼀次
     * @param[in] fd socket句柄
     * @return 是否删除成功
     */
    bool cancelAll(int fd);

    static IOManager* GetThis();

protected:
    /**
     * @brief 通知调度器有任务要调度
     * @details 写pipe让idle协程从epoll_wait退出，待idle协程yield之后Scheduler::run就可以调度其他任务
     * 如果当前没有空闲调度线程，那就没必要发通知
     */
    void tickle() override;

    bool stopping() override;

    /**
     * @brief idle协程
     * @details 对于IO协程调度来说，应阻塞在等待IO事件上，idle退出的时机是epoll_wait返回，对应的操作是tickle或注册的IO事件就绪
     * 调度器无调度任务时会阻塞idle协程上，对IO调度器⽽⾔，idle状态应该关注两件事，⼀是有没有新的调度任务，对应Schduler::schedule()，
     * 如果有新的调度任务，那应该立即退出idle状态，并执行对应的任务；二是关注当前注册的所有IO事件有没有触发，如果有触发，那么应该执⾏
     * IO事件对应的回调函数
     */
    void idle() override;
    void onTimerInsertedAtFront() override;
    void contextResize(size_t size);

private:
    // epoll文件句柄
    int m_epfd = 0;
    // pipe，fd[0] read, fd[1] write
    int m_tickleFds[2];
    // 当前等待执行的IO事件数量
    std::atomic<size_t> m_pendingEventCount = {0};
    // IOManager的Mutex
    std::shared_mutex m_mutex;
    // socket事件上下文数组
    std::vector<FdContext*> m_fdContexts;
};

}

#endif